%% 多文件曲线查看工具启动脚本
% 运行此脚本启动多文件曲线查看工具
% 支持同时打开多个CSV文件，并在同一图表中显示不同文件的曲线进行对比
% 新增功能：支持自定义公式计算，格式如 (A.1) + (B.2) * 3

function runMultiFilePlotViewer()
% 创建并启动多文件曲线查看工具

% 创建主窗口
fig = figure('Name', '多文件曲线查看工具 - MultiFile Plot Viewer', ...
             'Position', [100, 100, 1800, 1000], ...
             'MenuBar', 'none', ...
             'ToolBar', 'figure', ...
             'NumberTitle', 'off', ...
             'Resize', 'on', ...
             'CloseRequestFcn', @closeFigure);

% 设置窗口为全屏显示
set(fig, 'WindowState', 'maximized');

% 初始化数据结构
handles.fileData = {};           % 存储所有文件的数据
handles.fileNames = {};          % 存储文件名
handles.fileColors = {};         % 存储每个文件的颜色
handles.fileLineStyles = {};     % 存储每个文件的线型
handles.fileMarkers = {};        % 存储每个文件的标记
handles.selectedColumns = {};    % 存储每个文件选中的列
handles.columnNames = {};        % 存储每个文件的列名

% 新增：曲线计算相关数据结构
handles.calculatedCurves = {};   % 存储计算生成的曲线
handles.calculatedNames = {};    % 存储计算曲线的名称
handles.calculatedExpressions = {}; % 存储计算表达式

% 预定义更多颜色和线型组合，确保视觉区分度
handles.availableColors = {
    [0, 0.4470, 0.7410],    % 蓝色
    [0.8500, 0.3250, 0.0980], % 橙色
    [0.9290, 0.6940, 0.1250], % 黄色
    [0.4940, 0.1840, 0.5560], % 紫色
    [0.4660, 0.6740, 0.1880], % 绿色
    [0.3010, 0.7450, 0.9330], % 青色
    [0.6350, 0.0780, 0.1840], % 红色
    [0.2500, 0.2500, 0.2500], % 深灰色
    [0.8, 0.4, 0],           % 棕色
    [0.5, 0, 0.5],           % 深紫色
    [0, 0.5, 0.5],           % 深青色
    [0.7, 0.7, 0],           % 橄榄色
    [1, 0.4, 0.4],           % 粉红色
    [0.4, 0.8, 0.4],         % 浅绿色
    [0.4, 0.4, 1]            % 浅蓝色
};

handles.availableLineStyles = {'-', '--', ':', '-.', '-', '--', ':', '-.', '-', '--', ':', '-.', '-', '--', ':'};
handles.availableMarkers = {'none', 'o', 's', 'd', '^', 'v', '<', '>', 'p', 'h', 'none', 'o', 's', 'd', '^'};

% 创建面板 - 四列布局：文件管理(1.5/10)、列选择(1.5/10)、曲线计算(1/10)、图表显示(6/10)
panelFiles = uipanel('Parent', fig, ...
                     'Title', '文件管理', ...
                     'Position', [0.02, 0.95-0.3, 0.13,0.3], ...
                     'FontSize', 10);

panelCalculation = uipanel('Parent', fig, ...
                           'Title', '曲线计算', ...
                           'Position', [0.02, 0.05, 0.13, 0.60], ...
                           'FontSize', 10);

panelColumns = uipanel('Parent', fig, ...
                       'Title', '列选择', ...
                       'Position', [0.17, 0.05, 0.13, 0.90], ...
                       'FontSize', 10);

panelPlot = uipanel('Parent', fig, ...
                    'Title', '图表显示', ...
                    'Position', [0.32, 0.05, 0.66, 0.90], ...
                    'FontSize', 10);

pos1 = 200;
pos2 = 500;
pos3 = 720;
pos_dy = 30;

% 文件管理面板控件
handles.btnOpenFile = uicontrol('Parent', panelFiles, ...
                                'Style', 'pushbutton', ...
                                'String', '打开文件', ...
                                'Position', [5, pos1, 110, 25], ...
                                'Callback', @btnOpenFile_Callback);

handles.btnRemoveFile = uicontrol('Parent', panelFiles, ...
                                  'Style', 'pushbutton', ...
                                  'String', '删除文件', ...
                                  'Position', [5, pos1-pos_dy, 110, 25], ...
                                  'Callback', @btnRemoveFile_Callback);

handles.btnClearAll = uicontrol('Parent', panelFiles, ...
                                'Style', 'pushbutton', ...
                                'String', '清除所有', ...
                                'Position', [5, pos1-pos_dy*2, 110, 25], ...
                                'Callback', @btnClearAll_Callback);

handles.btnPlotData = uicontrol('Parent', panelFiles, ...
                                'Style', 'pushbutton', ...
                                'String', '绘制曲线', ...
                                'Position', [5, pos1-pos_dy*3, 110, 25], ...
                                'Callback', @btnPlotData_Callback);

uicontrol('Parent', panelFiles, ...
          'Style', 'text', ...
          'String', '已加载文件:', ...
          'Position', [5, pos1-pos_dy*4, 110, 20], ...
          'HorizontalAlignment', 'left');

handles.listboxFiles = uicontrol('Parent', panelFiles, ...
                                 'Style', 'listbox', ...
                                 'String', '', ...
                                 'Position', [5, pos1-pos_dy*6, 160, pos_dy*2], ...
                                 'Callback', @listboxFiles_Callback);

% 列选择面板控件
uicontrol('Parent', panelColumns, ...
          'Style', 'text', ...
          'String', '选择要显示的列:', ...
          'Position', [5, pos3, 120, 20], ...
          'HorizontalAlignment', 'left');

handles.btnSelectAll = uicontrol('Parent', panelColumns, ...
                                 'Style', 'pushbutton', ...
                                 'String', '全选', ...
                                 'Position', [5, pos3-pos_dy, 120, 20], ...
                                 'Callback', @btnSelectAll_Callback);

handles.btnDeselectAll = uicontrol('Parent', panelColumns, ...
                                   'Style', 'pushbutton', ...
                                   'String', '全取消', ...
                                   'Position', [5, pos3-pos_dy*2, 120, 20], ...
                                   'Callback', @btnDeselectAll_Callback);

handles.listboxColumns = uicontrol('Parent', panelColumns, ...
                                    'Style', 'listbox', ...
                                    'String', '', ...
                                    'Position', [5, 10, 180, 600], ...
                                    'Max', 2, ...  % 允许多选
                                    'Min', 0, ...  % 允许不选择
                                    'Callback', @listboxColumns_Callback);

% 曲线计算面板控件
uicontrol('Parent', panelCalculation, ...
          'Style', 'text', ...
          'String', '自定义公式:', ...
          'Position', [5, pos2-pos_dy, 180, 20], ...
          'HorizontalAlignment', 'left');

uicontrol('Parent', panelCalculation, ...
          'Style', 'text', ...
          'String', '格式: (A.1) + (B.2) * 3', ...
          'Position', [5, pos2-pos_dy*2, 180, 20], ...
          'HorizontalAlignment', 'left', ...
          'FontSize', 8);

handles.editExpression = uicontrol('Parent', panelCalculation, ...
                                   'Style', 'edit', ...
                                   'String', '(A.1) + (B.2) * 3', ...
                                   'Position', [5, pos2-pos_dy*4, 180, 40], ...
                                   'Max', 3, ...
                                   'Min', 1, ...
                                   'Style', 'edit', ...
                                   'Callback', @editExpression_Callback);

uicontrol('Parent', panelCalculation, ...
          'Style', 'text', ...
          'String', '可用变量:', ...
          'Position', [5, pos2-pos_dy*5, 180, 20], ...
          'HorizontalAlignment', 'left');

handles.textAvailableVars = uicontrol('Parent', panelCalculation, ...
                                      'Style', 'text', ...
                                      'String', 'A.1, A.2, B.1, B.2...', ...
                                      'Position', [5, pos2-pos_dy*7, 180, 40], ...
                                      'HorizontalAlignment', 'left', ...
                                      'FontSize', 8, ...
                                      'BackgroundColor', [0.95, 0.95, 0.95]);

handles.btnCalculate = uicontrol('Parent', panelCalculation, ...
                                 'Style', 'pushbutton', ...
                                 'String', '计算', ...
                                 'Position', [5, pos2-pos_dy*7.5, 80, 25], ...
                                 'Callback', @btnCalculate_Callback);

handles.btnRemoveCalc = uicontrol('Parent', panelCalculation, ...
                                  'Style', 'pushbutton', ...
                                  'String', '删除计算', ...
                                  'Position', [100, pos2-pos_dy*7.5, 80, 25], ...
                                  'Callback', @btnRemoveCalc_Callback);

uicontrol('Parent', panelCalculation, ...
          'Style', 'text', ...
          'String', '计算曲线:', ...
          'Position', [5, pos2-pos_dy*10, 80, 20], ...
          'HorizontalAlignment', 'left');

handles.listboxCalculated = uicontrol('Parent', panelCalculation, ...
                                      'Style', 'listbox', ...
                                      'String', '', ...
                                      'Position', [5, pos2-pos_dy*11, 180, 100], ...
                                      'Callback', @listboxCalculated_Callback);

% 图表显示面板控件
handles.axesPlot = axes('Parent', panelPlot, ...
                        'Position', [0.08, 0.15, 0.88, 0.80]);

% 在图表面板中添加控制按钮
uicontrol('Parent', panelPlot, ...
          'Style', 'text', ...
          'String', 'X轴范围:', ...
          'Position', [10, 10, 60, 20], ...
          'HorizontalAlignment', 'left');

handles.editXMin = uicontrol('Parent', panelPlot, ...
                             'Style', 'edit', ...
                             'String', '', ...
                             'Position', [70, 10, 50, 20], ...
                             'Callback', @editXMin_Callback);

uicontrol('Parent', panelPlot, ...
          'Style', 'text', ...
          'String', 'Y轴范围:', ...
          'Position', [130, 10, 60, 20], ...
          'HorizontalAlignment', 'left');

handles.editYMin = uicontrol('Parent', panelPlot, ...
                             'Style', 'edit', ...
                             'String', '', ...
                             'Position', [190, 10, 50, 20], ...
                             'Callback', @editYMin_Callback);

handles.btnAutoScale = uicontrol('Parent', panelPlot, ...
                                 'Style', 'pushbutton', ...
                                 'String', '自动缩放', ...
                                 'Position', [250, 10, 60, 20], ...
                                 'Callback', @btnAutoScale_Callback);

handles.btnExport = uicontrol('Parent', panelPlot, ...
                              'Style', 'pushbutton', ...
                              'String', '导出图表', ...
                              'Position', [320, 10, 60, 20], ...
                              'Callback', @btnExport_Callback);

% 保存所有句柄
handles.fig = fig;
guidata(fig, handles);

% 初始化图表
axes(handles.axesPlot);
xlabel('时间 (s)', 'FontSize', 10);
ylabel('数值', 'FontSize', 10);
title('多文件数据对比图', 'FontSize', 12);
grid on;

fprintf('多文件曲线查看工具已启动！\n');
fprintf('功能说明：\n');
fprintf('1. 点击"打开文件"按钮选择CSV文件\n');
fprintf('2. 在文件列表中选择文件，然后在列选择中选择要显示的列\n');
fprintf('3. 使用"全选"/"全取消"按钮快速操作列选择\n');
fprintf('4. 支持同时显示多个文件的不同列进行对比\n');
fprintf('5. 新增自定义公式计算功能：支持 (A.1) + (B.2) * 3 格式\n');
fprintf('6. 文件用字母编号：A、B、C...，列用数字编号：1、2、3...\n');
fprintf('7. 可以使用工具栏进行缩放、平移等操作\n');
fprintf('8. 点击"导出图表"保存当前图表\n');
fprintf('9. 每条曲线使用不同颜色，统一实线加小点风格\n');
fprintf('10. 四列布局：文件管理(1.5/10)、列选择(1.5/10)、曲线计算(1/10)、图表显示(6/10)\n');
fprintf('11. 文件编号显示：第一个文件为[A]，第二个为[B]，以此类推\n');

end

% 回调函数定义
function btnOpenFile_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    % 打开CSV文件
    [filename, pathname] = uigetfile({'*.csv', 'CSV Files (*.csv)'; ...
                                      '*.txt', 'Text Files (*.txt)'; ...
                                      '*.*', 'All Files (*.*)'}, ...
                                      '选择要打开的文件', 'MultiSelect', 'on');

    if isequal(filename, 0)
        return;
    end

    % 处理单个或多个文件
    if iscell(filename)
        fileList = filename;
    else
        fileList = {filename};
    end

    % 读取文件数据
    for i = 1:length(fileList)
        fullPath = fullfile(pathname, fileList{i});
        
        try
            % 尝试读取CSV文件，保留原始列名
            data = readtable(fullPath, 'VariableNamingRule', 'preserve');
            
            % 获取列名
            columnNames = data.Properties.VariableNames;
            
            % 转换为数值数组
            dataArray = table2array(data);
            
            % 存储数据
            fileIndex = length(handles.fileData) + 1;
            handles.fileData{fileIndex} = dataArray;
            handles.fileNames{fileIndex} = fileList{i};
            handles.columnNames{fileIndex} = columnNames;
            
            % 保留空的颜色数组以维持数据结构兼容性
            handles.fileColors{fileIndex} = [];
            handles.fileLineStyles{fileIndex} = [];
            handles.fileMarkers{fileIndex} = [];
            
            % 默认选择所有列
            handles.selectedColumns{fileIndex} = ones(1, length(columnNames));
            
            fprintf('成功加载文件: %s\n', fileList{i});
            
        catch ME
            errordlg(sprintf('无法读取文件 %s: %s', fileList{i}, ME.message), '文件读取错误');
        end
    end

    % 更新显示
    updateFileList(handles);
    updateColumnList(handles);
    updateAvailableVariables(handles);  % 更新可用变量显示
    
    % 新增：默认自动绘图，文件加载后立即显示数据
    plotData(handles);
    
    % 打印调试信息
    fprintf('文件加载完成，可用变量:\n');
    for fileIndex = 1:length(handles.fileData)
        if fileIndex <= length(handles.columnNames)
            fileLetter = char('A' + fileIndex - 1);
            columnNames = handles.columnNames{fileIndex};
            for colIndex = 1:length(columnNames)
                fprintf('  %s.%d: %s\n', fileLetter, colIndex, columnNames{colIndex});
            end
        end
    end
    
    % 更新句柄
    guidata(fig, handles);
end

function updateFileList(handles)
    if ~isempty(handles.fileNames)
        % 创建显示文本，添加字母编号前缀
        displayText = {};
        for i = 1:length(handles.fileNames)
            fileLetter = char('A' + i - 1);  % A, B, C, ...
            displayText{i} = sprintf('[%s] %s', fileLetter, handles.fileNames{i});
        end
        set(handles.listboxFiles, 'String', displayText);
        set(handles.listboxFiles, 'Value', 1);
    else
        set(handles.listboxFiles, 'String', '');
        set(handles.listboxFiles, 'Value', 1);
    end
end

function updateColumnList(handles)
    if ~isempty(handles.columnNames)
        currentFile = get(handles.listboxFiles, 'Value');
        if currentFile <= length(handles.columnNames)
            columnNames = handles.columnNames{currentFile};
            selectedColumns = handles.selectedColumns{currentFile};
            
            % 创建显示文本（使用字母编号和数字编号）
            displayText = {};
            fileLetter = char('A' + currentFile - 1);  % A, B, C, ...
            for i = 1:length(columnNames)
                displayText{i} = sprintf('[%s.%d] %s', fileLetter, i, columnNames{i});
            end
            set(handles.listboxColumns, 'String', displayText);
            
            % 设置选中项的背景颜色
            if ~isempty(selectedColumns)
                % 获取当前选中的列索引
                selectedIndices = find(selectedColumns);
                if ~isempty(selectedIndices)
                    % 设置选中项的背景颜色为浅蓝色
                    set(handles.listboxColumns, 'Value', selectedIndices);
                else
                    set(handles.listboxColumns, 'Value', []);
                end
            end
        end
    else
        set(handles.listboxColumns, 'String', '');
    end
end

function listboxFiles_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    updateColumnList(handles);
    guidata(fig, handles);
end

function listboxColumns_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    currentFile = get(handles.listboxFiles, 'Value');
    if currentFile <= length(handles.selectedColumns)
        % 单击切换（自锁开关）：忽略默认多选清空行为，仅对点击项做翻转
        prevSelected = find(handles.selectedColumns{currentFile});
        clicked = get(handles.listboxColumns, 'Value');
        if isempty(clicked)
            clickedIndex = [];
        else
            clickedIndex = clicked(1); % 单击时MATLAB会将Value置为当前项
        end

        if ~isempty(clickedIndex)
            if ismember(clickedIndex, prevSelected)
                % 若原本选中，则取消
                newSelected = setdiff(prevSelected, clickedIndex);
            else
                % 若原本未选中，则加入
                newSelected = union(prevSelected, clickedIndex);
            end
        else
            newSelected = prevSelected;
        end

        % 回写到数据与UI
        selVec = zeros(1, length(handles.selectedColumns{currentFile}));
        if ~isempty(newSelected)
            selVec(newSelected) = 1;
        end
        handles.selectedColumns{currentFile} = selVec;
        set(handles.listboxColumns, 'Value', newSelected);

        plotData(handles);
    end
    guidata(fig, handles);
end

function btnSelectAll_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    currentFile = get(handles.listboxFiles, 'Value');
    if currentFile <= length(handles.selectedColumns)
        % 全选当前文件的所有列
        handles.selectedColumns{currentFile} = ones(1, length(handles.selectedColumns{currentFile}));
        
        updateColumnList(handles);
        plotData(handles);
    end
    guidata(fig, handles);
end

function btnDeselectAll_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    currentFile = get(handles.listboxFiles, 'Value');
    if currentFile <= length(handles.selectedColumns)
        % 全取消当前文件的所有列
        handles.selectedColumns{currentFile} = zeros(1, length(handles.selectedColumns{currentFile}));
        
        updateColumnList(handles);
        plotData(handles);
    end
    guidata(fig, handles);
end

function plotData(handles)
    axes(handles.axesPlot);
    cla;

    if isempty(handles.fileData) && isempty(handles.calculatedCurves)
        return;
    end

    hold on;
    legendEntries = {};
    plotCounter = 0;  % 用于跟踪总的绘图数量
    plottedLines = [];  % 收集所有绘制的线对象句柄

    % 绘制文件中的曲线
    for fileIndex = 1:length(handles.fileData)
        if fileIndex <= length(handles.selectedColumns)
            data = handles.fileData{fileIndex};
            selectedCols = handles.selectedColumns{fileIndex};
            columnNames = handles.columnNames{fileIndex};
            
            % 绘制选中的列
            for colIndex = 1:length(selectedCols)
                if selectedCols(colIndex) && colIndex <= size(data, 2)
                    xData = data(:, 1);  % 第一列作为X轴
                    yData = data(:, colIndex);
                    
                    plotCounter = plotCounter + 1;
                    
                    % 为每个曲线分配独特的颜色，但使用统一的实线加小点风格
                    colorIndex = mod(plotCounter - 1, length(handles.availableColors)) + 1;
                    
                    % 绘制曲线 - 统一使用实线加小点风格
                    hLine = plot(xData, yData, 'Color', handles.availableColors{colorIndex}, ...
                         'LineStyle', '-', ...           % 统一使用实线
                         'Marker', 'o', ...              % 统一使用圆形标记
                         'LineWidth', 2.0, 'MarkerSize', 4);  % 适中的线条和标记大小
                    
                    % 添加图例条目
                    legendText = sprintf('%s - %s', handles.fileNames{fileIndex}, columnNames{colIndex});
                    legendEntries{end+1} = legendText;
                    try
                        set(hLine, 'DisplayName', legendText);
                    catch
                    end
                    plottedLines(end+1) = hLine; %#ok<AGROW>
                end
            end
        end
    end

    % 绘制计算生成的曲线
    for calcIndex = 1:length(handles.calculatedCurves)
        if ~isempty(handles.calculatedCurves{calcIndex})
            % 使用第一个文件的时间轴作为X轴
            if ~isempty(handles.fileData)
                xData = handles.fileData{1}(:, 1);
            else
                xData = 1:length(handles.calculatedCurves{calcIndex});
            end
            
            yData = handles.calculatedCurves{calcIndex};
            % 统一形状：x为列向量；y为二维矩阵（按行与x对齐）
            xData = xData(:);
            if isvector(yData)
                % 将行向量转为列向量
                if size(yData, 1) == 1 && size(yData, 2) > 1
                    yData = yData.';
                end
            end
            
            % 行数对齐规则：
            % - y为标量：广播到与x同样的行数
            % - y为矩阵：按行与x对齐（截断到相同行数）
            if isempty(yData)
                continue;  % 无数据则跳过
            elseif isscalar(yData)
                if ~isempty(xData)
                    yData = repmat(yData, length(xData), 1);
                end
            else
                yRows = size(yData, 1);
                xRows = length(xData);
                if xRows ~= yRows
                    minRows = min(xRows, yRows);
                    xData = xData(1:minRows, 1);
                    yData = yData(1:minRows, :);
                end
            end
            
            plotCounter = plotCounter + 1;
            
            % 为计算曲线分配颜色
            colorIndex = mod(plotCounter - 1, length(handles.availableColors)) + 1;
            
            % 绘制计算曲线 - 使用实线加点统一形式
            hLine = plot(xData, yData, 'Color', handles.availableColors{colorIndex}, ...
                 'LineStyle', '-', ...            % 使用实线
                 'Marker', 'o', ...               % 使用圆形标记
                 'LineWidth', 2.0, 'MarkerSize', 4);  % 适中的线条和标记大小
            
            % 添加图例条目
            legendText = sprintf('[计算] %s', handles.calculatedNames{calcIndex});
            legendEntries{end+1} = legendText;
            try
                set(hLine, 'DisplayName', legendText);
            catch
            end
            plottedLines(end+1) = hLine; %#ok<AGROW>
        end
    end

    hold off;

    % 添加图例
    if ~isempty(legendEntries)
        try
            hLeg = legend('show');
            set(hLeg, 'Location', 'best', 'Interpreter', 'none', 'FontSize', 9);
        catch
            hLeg = legend(legendEntries, 'Location', 'best', 'Interpreter', 'none', 'FontSize', 9);
        end
        % 设置交互与初始颜色（显示状态 -> 灰色文字）
        try
            set(hLeg, 'ItemHitFcn', @(src, evt) localLegendItemClick(src, evt));
            localRefreshLegendTextColors(hLeg);
        catch
        end
    end

    % 设置坐标轴标签
    xlabel('时间 (s)', 'FontSize', 11);
    ylabel('数值', 'FontSize', 11);
    title('多文件数据对比图', 'FontSize', 13, 'FontWeight', 'bold');
    grid on;
    
    % 设置坐标轴字体大小
    set(gca, 'FontSize', 10);
    
    % 坐标轴刻度格式：禁用科学计数法并显示小数
    ax = gca;
    try
        % 新版首选：直接指定刻度格式（6位小数）
        xtickformat('%.6f');
        ytickformat('%.6f');
    catch
        % 兼容旧版：手动生成刻度标签
        set(ax, 'XTickLabel', arrayfun(@(v) sprintf('%.6f', v), get(ax, 'XTick'), 'UniformOutput', false));
        set(ax, 'YTickLabel', arrayfun(@(v) sprintf('%.6f', v), get(ax, 'YTick'), 'UniformOutput', false));
    end
    % 去除坐标轴的指数缩放（例如 ×10^n）
    try
        ax.XAxis.Exponent = 0;
        ax.YAxis.Exponent = 0;
    catch
        % 忽略不支持该属性的版本
    end
    
    % 数据提示（DataTip）显示为固定小数，不使用科学计数法
    try
        dcm = datacursormode(ancestor(ax, 'figure'));
        set(dcm, 'UpdateFcn', @localCustomDataTipFormatter);
    catch
        % 忽略旧版本不支持 datacursormode 的情况
    end
end

function btnRemoveFile_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    currentFile = get(handles.listboxFiles, 'Value');
    if currentFile <= length(handles.fileData)
        % 删除选中的文件数据
        handles.fileData(currentFile) = [];
        handles.fileNames(currentFile) = [];
        handles.fileColors(currentFile) = [];
        handles.fileLineStyles(currentFile) = [];
        handles.fileMarkers(currentFile) = [];
        handles.selectedColumns(currentFile) = [];
        handles.columnNames(currentFile) = [];
        
        updateFileList(handles);
        updateColumnList(handles);
        updateAvailableVariables(handles);  % 更新可用变量显示
        plotData(handles);
    end
    guidata(fig, handles);
end

function btnClearAll_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    handles.fileData = {};
    handles.fileNames = {};
    handles.fileColors = {};
    handles.fileLineStyles = {};
    handles.fileMarkers = {};
    handles.selectedColumns = {};
    handles.columnNames = {};
    
    % 新增：清除计算曲线
    handles.calculatedCurves = {};
    handles.calculatedNames = {};
    handles.calculatedExpressions = {};

    updateFileList(handles);
    updateColumnList(handles);
    updateAvailableVariables(handles);  % 更新可用变量显示
    updateCalculatedCurvesList(handles); % 新增：更新计算曲线列表
    plotData(handles);

    guidata(fig, handles);
end

function btnPlotData_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    % 手动绘制曲线
    plotData(handles);
    
    % 更新句柄
    guidata(fig, handles);
end

function btnExport_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    [filename, pathname] = uiputfile({'*.png', 'PNG Files (*.png)'; ...
                                      '*.jpg', 'JPEG Files (*.jpg)'; ...
                                      '*.pdf', 'PDF Files (*.pdf)'; ...
                                      '*.fig', 'MATLAB Figure Files (*.fig)'}, ...
                                      '保存图表');
    if ~isequal(filename, 0)
        fullPath = fullfile(pathname, filename);
        saveas(handles.axesPlot, fullPath);
        fprintf('图表已保存到: %s\n', fullPath);
    end
end

function editXMin_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    try
        xMin = str2double(get(hObject, 'String'));
        xlim(handles.axesPlot, [xMin, inf]);
    catch
        errordlg('请输入有效的数值', '输入错误');
    end
end

function editYMin_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    try
        yMin = str2double(get(hObject, 'String'));
        ylim(handles.axesPlot, [yMin, inf]);
    catch
        errordlg('请输入有效的数值', '输入错误');
    end
end

function btnAutoScale_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    axes(handles.axesPlot);
    axis auto;
    grid on;
end

function closeFigure(src, event)
    delete(src);
end

% 新增：曲线计算相关回调函数

function editExpression_Callback(hObject, eventdata)
    % 表达式输入框回调函数
    % 这里可以添加表达式验证逻辑
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    % 可以在这里添加实时语法检查等功能
    guidata(fig, handles);
end

% 自定义数据提示格式函数：固定小数显示
function txt = localCustomDataTipFormatter(~, event_obj)
    pos = get(event_obj, 'Position');
    % pos 可能为 [x y] 或 [x y z]
    if numel(pos) >= 2
        txt = {sprintf('X: %.6f', pos(1)), sprintf('Y: %.6f', pos(2))};
        if numel(pos) >= 3
            txt{end+1} = sprintf('Z: %.6f', pos(3));
        end
    else
        txt = {sprintf('Value: %.6f', pos(1))};
    end
end

% 图例项点击回调：切换曲线可见性，并刷新文字颜色
function localLegendItemClick(src, evt)
    try
        hLine = evt.Peer;
        if strcmp(get(hLine, 'Visible'), 'on')
            set(hLine, 'Visible', 'off');
        else
            set(hLine, 'Visible', 'on');
        end
        localRefreshLegendTextColors(src);
    catch
    end
end

% 刷新图例文字颜色：可见为灰色，不可见为黑色（或加深）
function localRefreshLegendTextColors(hLeg)
    try
        % 获取图例中每个条目对应的绘图对象
        legItems = hLeg.EntryContainer.Children;  % R2018a+
        for i = 1:numel(legItems)
            try
                peer = legItems(i).Icon.Transform.Children.Children; % 对应到线对象
                % 获取真实Line句柄
                hLine = []; %#ok<NASGU>
            catch
            end
            % MATLAB图例结构较复杂，改用显示名匹配
            label = '';
            try
                label = legItems(i).Label.String;
            catch
            end
            if ~isempty(label)
                % 在当前坐标轴中按DisplayName寻找
                ax = ancestor(hLeg, 'axes');
                lines = findobj(ax, '-property', 'DisplayName');
                h = findobj(lines, 'flat', 'DisplayName', label);
                if ~isempty(h)
                    isVisible = strcmp(get(h(1), 'Visible'), 'on');
                    if isVisible
                        legItems(i).Label.Color = [0.5 0.5 0.5]; % 灰色
                    else
                        legItems(i).Label.Color = [0 0 0]; % 黑色
                    end
                end
            end
        end
    catch
        % 兼容旧版：忽略
    end
end

function btnCalculate_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    % 获取自定义公式
    formula = get(handles.editExpression, 'String');
    if isempty(formula)
        errordlg('请输入计算公式', '错误');
        return;
    end
    
    try
        % 解析并计算公式 - 使用简化版本
        [resultData, expression] = evaluateFormulaSimple(handles, formula);
        
        % 生成计算曲线名称
        calcName = sprintf('计算: %s', formula);
        
        % 存储计算结果
        handles.calculatedCurves{end+1} = resultData;
        handles.calculatedNames{end+1} = calcName;
        handles.calculatedExpressions{end+1} = expression;
        
        % 更新显示
        updateCalculatedCurvesList(handles);
        updateAvailableVariables(handles);  % 更新可用变量显示
        plotData(handles);
        
        fprintf('计算完成: %s\n', calcName);
        guidata(fig, handles);
        
    catch ME
        errordlg(sprintf('公式计算错误: %s', ME.message), '错误');
    end
end

function btnRemoveCalc_Callback(hObject, eventdata)
    % 获取当前句柄
    fig = ancestor(hObject, 'figure');
    handles = guidata(fig);
    
    currentCalc = get(handles.listboxCalculated, 'Value');
    if currentCalc <= length(handles.calculatedCurves)
        % 删除选中的计算曲线
        handles.calculatedCurves(currentCalc) = [];
        handles.calculatedNames(currentCalc) = [];
        handles.calculatedExpressions(currentCalc) = [];
        
        updateCalculatedCurvesList(handles);
        updateAvailableVariables(handles);  % 更新可用变量显示
        plotData(handles);
    end
    guidata(fig, handles);
end

function listboxCalculated_Callback(hObject, eventdata)
    % 计算曲线列表回调函数
    % 可以在这里添加显示计算表达式等功能
end

% 新增：曲线计算辅助函数

function updateCalculatedCurvesList(handles)
    % 更新计算曲线列表
    if ~isempty(handles.calculatedNames)
        set(handles.listboxCalculated, 'String', handles.calculatedNames);
        set(handles.listboxCalculated, 'Value', 1);
    else
        set(handles.listboxCalculated, 'String', '');
        set(handles.listboxCalculated, 'Value', 1);
    end
    
    % 确保Value值在有效范围内
    currentValue = get(handles.listboxCalculated, 'Value');
    maxValue = length(get(handles.listboxCalculated, 'String'));
    if maxValue > 0 && currentValue > maxValue
        set(handles.listboxCalculated, 'Value', 1);
    end
end

function allCurves = getAllAvailableCurves(handles)
    % 获取所有可用曲线数据（用于自定义表达式）
    allCurves = {};
    
    % 获取文件中的所有列数据（不需要先选择，但排除时间列）
    for fileIndex = 1:length(handles.fileData)
        if fileIndex <= length(handles.columnNames)
            data = handles.fileData{fileIndex};
            columnNames = handles.columnNames{fileIndex};
            
            for colIndex = 1:length(columnNames)
                % 跳过第一列（时间列），只包含数据列
                if colIndex > 1 && colIndex <= size(data, 2)
                    allCurves{end+1} = data(:, colIndex);
                end
            end
        end
    end
    
    % 添加计算曲线数据
    allCurves = [allCurves, handles.calculatedCurves];
end

function [resultData, expression] = evaluateFormula(handles, formula)
    % 解析并计算公式，支持 (A.1) + (B.2) * 3 格式
    
    % 首先找到所有变量引用，如 (A.1), (B.2) 等
    variablePattern = '\(([A-Z])\.(\d+)\)';
    matches = regexp(formula, variablePattern, 'tokens');
    
    if isempty(matches)
        error('未找到有效的变量引用，请使用格式如 (A.1), (B.2) 等');
    end
    
    % 获取第一个变量的长度作为基准长度
    firstMatch = matches{1};
    fileLetter = firstMatch{1};
    colNum = str2double(firstMatch{2});
    
    fileIndex = fileLetter - 'A' + 1;  % A=1, B=2, C=3, ...
    
    if fileIndex > length(handles.fileData)
        error('文件索引超出范围: %s', fileLetter);
    end
    
    data = handles.fileData{fileIndex};
    if colNum > size(data, 2)
        error('列索引超出范围: %s.%d', fileLetter, colNum);
    end
    
    % 使用第一个变量的长度作为基准
    baseLength = length(data(:, colNum));
    resultData = zeros(baseLength, 1);
    
    % 替换公式中的变量引用为实际数据
    workingFormula = formula;
    
    % 创建临时变量存储数据
    tempVars = struct();
    
    for i = 1:length(matches)
        match = matches{i};
        fileLetter = match{1};
        colNum = str2double(match{2});
        
        fileIndex = fileLetter - 'A' + 1;
        
        if fileIndex > length(handles.fileData)
            error('文件索引超出范围: %s', fileLetter);
        end
        
        data = handles.fileData{fileIndex};
        if colNum > size(data, 2)
            error('列索引超出范围: %s.%d', fileLetter, colNum);
        end
        
        % 获取数据并确保长度一致
        varData = data(:, colNum);
        if length(varData) ~= baseLength
            % 如果长度不同，截取到较短的长度
            minLength = min(length(varData), baseLength);
            varData = varData(1:minLength);
            resultData = resultData(1:minLength);
            baseLength = minLength;
        end
        
        % 创建变量名并存储到临时结构体中
        varName = sprintf('var_%s_%d', fileLetter, colNum);
        tempVars.(varName) = varData;
        
        % 替换公式中的变量引用 - 使用更精确的替换
        oldPattern = sprintf('(%s.%d)', fileLetter, colNum);
        workingFormula = strrep(workingFormula, oldPattern, varName);
        
        % 调试信息
        fprintf('调试: 替换 %s -> %s\n', oldPattern, varName);
    end
    
    % 执行公式，使用临时变量
    try
        % 将临时变量添加到当前工作空间
        varNames = fieldnames(tempVars);
        for i = 1:length(varNames)
            assignin('caller', varNames{i}, tempVars.(varNames{i}));
        end
        
        % 调试信息
        fprintf('调试: 工作公式 = %s\n', workingFormula);
        fprintf('调试: 可用变量 = %s\n', strjoin(varNames, ', '));
        
        % 使用eval执行公式
        resultData = eval(workingFormula);
        expression = workingFormula;
        
        % 清理临时变量
        for i = 1:length(varNames)
            evalin('caller', sprintf('clear %s', varNames{i}));
        end
    catch ME
        % 清理临时变量
        if exist('varNames', 'var')
            for i = 1:length(varNames)
                evalin('caller', sprintf('clear %s', varNames{i}));
            end
        end
        fprintf('错误详情: %s\n', ME.message);
        fprintf('错误位置: %s\n', ME.stack(1).name);
        error('公式执行错误: %s', ME.message);
    end
end

function [resultData, expression] = evaluateFormulaSimple(handles, formula)
    % 简化版本的公式计算函数，直接计算
    try
        % 使用正则表达式找到所有变量引用
        pattern = '\(([A-Z])\.(\d+)\)';
        matches = regexp(formula, pattern, 'tokens');
        
        if isempty(matches)
            error('公式中没有找到有效的变量引用，请使用格式如 (A.1), (B.2) 等');
        end
        
        % 先遍历所有变量，计算最小长度（忽略标量，避免被标量把最小长度压到1）
        allLengths = [];
        for i = 1:length(matches)
            fileLetter_i = matches{i}{1};
            colNum_i = str2double(matches{i}{2});
            fileIndex_i = fileLetter_i - 'A' + 1;
            
            if fileIndex_i > length(handles.fileData) || colNum_i > size(handles.fileData{fileIndex_i}, 2)
                error('变量引用超出范围: %s.%d', fileLetter_i, colNum_i);
            end
            data_i = handles.fileData{fileIndex_i}(:, colNum_i);
            len_i = length(data_i);
            if len_i > 1
                allLengths(end+1) = len_i; %#ok<AGROW>
            end
        end
        if isempty(allLengths)
            minLength = 1;  % 全是标量时，结果长度为1
        else
            minLength = min(allLengths);
        end
        
        % 创建变量映射
        varMap = containers.Map();
        
        % 处理所有变量引用
        for i = 1:length(matches)
            fileLetter = matches{i}{1};
            colNum = str2double(matches{i}{2});
            fileIndex = fileLetter - 'A' + 1;
            
            if fileIndex > length(handles.fileData) || colNum > size(handles.fileData{fileIndex}, 2)
                error('变量引用超出范围: %s.%d', fileLetter, colNum);
            end
            
            % 获取数据并根据最小长度对齐（支持标量广播）
            data = handles.fileData{fileIndex}(:, colNum);
            dataLen = length(data);
            if dataLen == 1
                % 标量广播到 minLength
                data = repmat(data, minLength, 1);
            elseif dataLen > minLength
                % 截断到 minLength
                data = data(1:minLength);
            else
                % 与 minLength 相等时保持不变；小于 minLength 的情况按预处理逻辑不会出现
            end
            
            % 存储变量数据
            varKey = sprintf('(%s.%d)', fileLetter, colNum);
            varMap(varKey) = data;
        end
        
        fprintf('调试信息 - 原始公式: %s\n', formula);
        fprintf('调试信息 - 可用变量: %s\n', strjoin(varMap.keys(), ', '));
        
        % 直接计算结果
        resultData = evaluateExpressionDirect(formula, varMap);
        expression = formula;
        
    catch ME
        fprintf('错误详情: %s\n', ME.message);
        fprintf('错误位置: %s\n', ME.stack(1).name);
        rethrow(ME);
    end
end

function result = evaluateExpressionDirect(formula, varMap)
    % 直接计算表达式的值，支持向量运算
    % 支持的运算符: +, -, *, /, ^, (, )
    
    % 移除所有空格
    formula = strrep(formula, ' ', '');
    
    % 获取第一个变量的长度作为基准
    firstVar = varMap.values();
    if isempty(firstVar)
        error('没有可用的变量数据');
    end
    baseLength = length(firstVar{1});
    
    % 创建临时变量并分配到当前工作空间
    tempVars = {};
    workingFormula = formula;
    
    for varKey = varMap.keys()
        varData = varMap(varKey{1});
        % 创建简单的临时变量名，格式如 temp_A_1
        tempVarName = sprintf('temp_%s', strrep(varKey{1}, '(', ''));  % 移除左括号
        tempVarName = strrep(tempVarName, ')', '');  % 移除右括号
        tempVarName = strrep(tempVarName, '.', '_');  % 将点替换为下划线
        tempVars{end+1} = tempVarName;
        
        % 将变量数据分配到当前工作空间
        assignin('caller', tempVarName, varData);
        
        % 替换公式中的变量引用
        workingFormula = strrep(workingFormula, varKey{1}, tempVarName);
    end
    
    fprintf('调试信息 - 工作公式: %s\n', workingFormula);
    fprintf('调试信息 - 临时变量: %s\n', strjoin(tempVars, ', '));
    
    % 使用eval计算最终结果
    try
        % 使用evalin('caller', ...) 来确保在正确的作用域中执行
        result = evalin('caller', workingFormula);
        
        % 清理临时变量
        for i = 1:length(tempVars)
            try
                evalin('caller', sprintf('clear %s', tempVars{i}));
            catch
                % 忽略清理错误
            end
        end
        
    catch ME
        % 清理临时变量
        for i = 1:length(tempVars)
            try
                evalin('caller', sprintf('clear %s', tempVars{i}));
            catch
                % 忽略清理错误
            end
        end
        error('公式计算失败: %s', ME.message);
    end
end

function updateAvailableVariables(handles)
    % 更新可用变量显示
    if isempty(handles.fileData)
        set(handles.textAvailableVars, 'String', '无可用变量');
        return;
    end
    
    varList = {};
    for fileIndex = 1:length(handles.fileData)
        if fileIndex <= length(handles.columnNames)
            fileLetter = char('A' + fileIndex - 1);
            columnNames = handles.columnNames{fileIndex};
            
            for colIndex = 1:length(columnNames)
                varList{end+1} = sprintf('%s.%d', fileLetter, colIndex);
            end
        end
    end
    
    % 添加计算曲线变量
    for i = 1:length(handles.calculatedNames)
        varList{end+1} = sprintf('Calc%d', i);
    end
    
    % 限制显示长度，避免界面过于拥挤
    if length(varList) > 10
        displayVars = [varList(1:10), '...'];
    else
        displayVars = varList;
    end
    
    set(handles.textAvailableVars, 'String', strjoin(displayVars, ', '));
end 